package cn.amossun.starter.event.handler;

import com.alibaba.fastjson.JSON;
import cn.amossun.starter.event.annoation.EventMulticaster;
import cn.amossun.starter.event.annoation.EventBinding;
import cn.amossun.starter.event.common.Constants;
import cn.amossun.starter.event.exception.EventPushRemoteException;
import cn.amossun.starter.event.property.SimpleEventMessage;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Slf4j
@Aspect
public class SimpleMulticasterAspect implements RemoteEventMessagePublisher<SimpleEventMessage>{

    @Value("${spring.application.name}")
    private String service;

    @Autowired
    @Qualifier(value = "instance")
    private String instance;

    @Autowired
    @Qualifier("eventRabbitTemplate")
    RabbitTemplate rabbitTemplate;

    @Pointcut("@annotation(cn.amossun.starter.event.annoation.EventMulticaster)")
    public void pointcut() {
        log.info("拦截广播事件");
    }

    @AfterReturning(pointcut = "pointcut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) throws Throwable {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        EventMulticaster eventMulticaster = AnnotationUtils.findAnnotation(method, EventMulticaster.class);

        boolean thrownException = eventMulticaster.thrownException();

        try {
            String message = JSON.toJSONString(result);

            List<EventBinding> eventBindingList = Arrays.stream(eventMulticaster.bindings()).collect(Collectors.toList());
            eventBindingList.stream().forEach(eventBinding -> {

                SimpleEventMessage eventMessage = eventMessage(service, instance, eventBinding.key(),
                                                               eventMulticaster.definition(), eventMulticaster.description(),
                                                               method.getName(), message, getArguments(eventBinding.arguments()));

                String routingKey = String.format(Constants.ROUTING_KEY_TEMPLATE, eventBinding.key(), eventMulticaster.definition().getCode());

                pushRemoteEvent(eventBinding.exchange().name(), routingKey, eventMessage);
            });

            log.info("广播事件成功");

        } catch (Exception e) {
            log.error("广播事件失败: ", e);
            if (thrownException) {
                throw e;
            }
        }
    }

    @Override
    public void pushRemoteEvent(String exchangeName, String routingKey, SimpleEventMessage event) {

        String eventStr = JSON.toJSONString(event);

        log.info("开始发布广播事件: routing key: [{}] => {}", routingKey, event);

        CorrelationData correlationData = new CorrelationData(String.format(Constants.QUEUE_UNIQUE_TEMPLATE, routingKey, UUID.randomUUID().toString()));
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType(Constants.CONTENT_TYPE_JSON);
        messageProperties.setContentEncoding(Constants.DEFAULT_CHARSET);
        Message rabbitMessage = new Message(eventStr.getBytes(Charset.forName(Constants.DEFAULT_CHARSET)), messageProperties);

        try {
            rabbitTemplate.send(exchangeName, routingKey, rabbitMessage, correlationData);
            log.info("发布广播事件成功: routing key: [{}] => {}", routingKey, event);
        } catch (Exception e) {
            throw new EventPushRemoteException(e.getMessage());
        }
    }
}
